/**
 * 
 */
package org.vsg.stock.basic.service.business;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vsg.stock.basic.domain.BalanceLine;
import org.vsg.stock.basic.domain.BalanceLineFactory;
import org.vsg.stock.basic.domain.BalanceLineKeyPoint;
import org.vsg.stock.basic.service.StockBusinessHelper;
import org.vsg.stock.core.domain.StockDailyKLine;

/**
 * @author Bill Vison
 *
 */
public class BasicBalanceLineFinderBO extends
	OrgDataStockBalanceLineFinderBO {

	private static final Logger logger = LoggerFactory.getLogger(BasicBalanceLineFinderBO.class);
	
	private BalanceLineFactory factory = BalanceLineFactory.getInstance();
	
	private static final String FINDER_CODE = "basic_balance_line";
	
	
	
	@Override
	public String getFinderCode() {
		// TODO Auto-generated method stub
		return FINDER_CODE;
	}

	@Override
	protected List<BalanceLine> scanLines(List<StockDailyKLine>  stkKlineList) {
		
		// --- scan and mark point in to list and map ---
		Map<Double,Set<GoupingPointRefStockDailyKLine>> groupingFinalMap = groupFinalPriceMap(stkKlineList);
		
		// --- sort map ---
		//listLinePoint = sortByKey(listLinePoint);
		
		// --- Redistribution price ---
		List<BalanceLine> allNewLines = new Vector<BalanceLine>();			
		// --- build group line & point ---
		Entry<Double, Set<GoupingPointRefStockDailyKLine>> entry = null;
		
		for (Iterator<Entry<Double, Set<GoupingPointRefStockDailyKLine>>> iter = groupingFinalMap.entrySet().iterator() ; iter.hasNext(); ) {
			
			entry = iter.next();
			
			Set<GoupingPointRefStockDailyKLine> values = entry.getValue();
			
			BalanceLine aNewLine = factory.createLine(stkKlineList.get(0).getId().getStockCode(), BalanceLine.FORM_H);
			aNewLine.setForm(BalanceLine.FORM_H);
			for (Iterator<GoupingPointRefStockDailyKLine> klineIter = values.iterator() ; klineIter.hasNext(); ) {
				GoupingPointRefStockDailyKLine groupingDailyKline = klineIter.next();
				BalanceLineKeyPoint blkPoint = factory.createPoint(groupingDailyKline.getReferPrice(), groupingDailyKline);
				
				aNewLine.addKeyPoint( blkPoint );				
			}
			allNewLines.add(aNewLine);			

		}



		return allNewLines;
		

	}

	
	private Map<Double, Set<GoupingPointRefStockDailyKLine>> groupFinalPriceMap(List<StockDailyKLine> stkKlineList) {
		
		Map<Double, List<GoupingPointRefStockDailyKLine>> groupingKLineByPrice = firstGroupPointForLine(stkKlineList);
		
		// ---- remove mark noise data ----
		Iterator<Map.Entry<Double,List<GoupingPointRefStockDailyKLine>>> entryIter = groupingKLineByPrice.entrySet().iterator();
		while (entryIter.hasNext() ) {
			Map.Entry<Double,List<GoupingPointRefStockDailyKLine>>  entry =  entryIter.next();
			
			List<GoupingPointRefStockDailyKLine> listKLineList =  entry.getValue();
			
			if (listKLineList.size() == 1) {
				double keyYield = listKLineList.get(0).getYieldRate();
				// --- it is key point --- 
				if (  Math.abs( keyYield ) * 100 < 3.5 ) {
					// --- remove this data ---
					entryIter.remove();
				} 
			}			
		}
		// --- find the map data ---
		Map<GoupingPointRefStockDailyKLine , List<Double>> refStkDailymap = convertToKlinePriceMap(groupingKLineByPrice);
		
		Map<Double, Set<GoupingPointRefStockDailyKLine>> finalGroupingKLineMap = new HashMap<Double,Set<GoupingPointRefStockDailyKLine>> ();
		
		// ---- merge to online line to defined ----
		Set<GoupingPointRefStockDailyKLine> refStkKLineSet = refStkDailymap.keySet();
		List<GoupingPointRefStockDailyKLine> groupingSortList = null;
		
		for (GoupingPointRefStockDailyKLine kline : refStkKLineSet) {
			
			List<Double> priceList = refStkDailymap.get(kline) ;
			
			Set<GoupingPointRefStockDailyKLine> groupingSortHashSet = new LinkedHashSet<GoupingPointRefStockDailyKLine>();
			
			for (int i = 0 ; i < priceList.size() ; i++) {
				groupingSortHashSet.addAll( groupingKLineByPrice.get( priceList.get(i) ) );
			}
			
			groupingSortList = new LinkedList<GoupingPointRefStockDailyKLine>(groupingSortHashSet);
			if ( groupingSortList.size() > 2) {
				Collections.sort(groupingSortList ,new GoupingPointStockDailyKLineComparator());
			}
			
			// --- find the middle point and group in to a new map  ---
			int modIndex = groupingSortList.size() % 2;
			int findIndex = groupingSortList.size() / 2;
			double standPrice = 0;
			if (modIndex != 0) {
				GoupingPointRefStockDailyKLine gkline = groupingSortList.get(findIndex);
				standPrice = gkline.getReferPrice();
			}
			else {
				standPrice = ( groupingSortList.get(findIndex-1).getReferPrice() +  groupingSortList.get(findIndex).getReferPrice() ) / 2;
			}
			
			// ---- grouping for the new price ---
			double max = StockBusinessHelper.getDiffRelativedPrice(standPrice, StockBusinessHelper.MAX_DIFF);
			double min = StockBusinessHelper.getDiffRelativedPrice(standPrice, StockBusinessHelper.MIN_DIFF);			
			
			// --- map a new set ---
			Set<GoupingPointRefStockDailyKLine> finalGroupingSet = new LinkedHashSet<GoupingPointRefStockDailyKLine>();
			for (GoupingPointRefStockDailyKLine tmp : groupingSortList) {
				if (tmp.getReferPrice() <= max && tmp.getReferPrice() >= min) {
					finalGroupingSet.add( tmp );
				}
			}
			finalGroupingKLineMap.put( standPrice , finalGroupingSet);
			
		}				
		
		return finalGroupingKLineMap;
	}
	
	private Map<GoupingPointRefStockDailyKLine , List<Double>> convertToKlinePriceMap(Map<Double, List<GoupingPointRefStockDailyKLine>>  groupingKLineByPrice) {
		
		Map<GoupingPointRefStockDailyKLine , List<Double>> klinePriceMap = new HashMap<GoupingPointRefStockDailyKLine , List<Double>>();
		
		
		for (Iterator<Double>  pricekeyIter =  groupingKLineByPrice.keySet().iterator() ; pricekeyIter.hasNext() ; ) {
			Double price = pricekeyIter.next();
			List<GoupingPointRefStockDailyKLine> klineList = groupingKLineByPrice.get(price);
			// --- read the line ---
			for (GoupingPointRefStockDailyKLine dailyKLine : klineList) {
				
				List<Double> assoList = klinePriceMap.get( dailyKLine );
				if (assoList == null) {
					assoList = new Vector<Double>();
				}
				if ( !assoList.contains( price ) ) {
					assoList.add( price );
				}
				klinePriceMap.put( dailyKLine, assoList);
			}
			
		}
		
		return klinePriceMap;
	}
	
	
	
	private Map<Double, List<GoupingPointRefStockDailyKLine>> firstGroupPointForLine(List<StockDailyKLine> stkKlineList) {
		
		Map<Double,List<GoupingPointRefStockDailyKLine>> groupingKLineByPrice = new HashMap<Double,List<GoupingPointRefStockDailyKLine>> ();
		

		for (StockDailyKLine kline : stkKlineList) {
			
			/*
			if (df.format( kline.getDate()).equals("2009-08-17")) {
				System.out.println("mark");
			}
			*/

			// --- merge price ---
			Map<Double,String> mergePriceRecord = StockBusinessHelper.mergeToPriceMap(kline);
			
			Set<Double> mergePriceKeies = mergePriceRecord.keySet();
			
			for (double mergePrice : mergePriceKeies) {
				
				double output = searchGroupingLinePrice(groupingKLineByPrice , mergePrice);
				
				List<GoupingPointRefStockDailyKLine> listContainer = groupingKLineByPrice.get(output);
				if (listContainer == null) {
					 listContainer = new Vector<GoupingPointRefStockDailyKLine>();
				}
				if (!listContainer.contains( kline )) {
					GoupingPointRefStockDailyKLine gprKline = new GoupingPointRefStockDailyKLine(kline , output);
					if ( !listContainer.contains( gprKline ) ) {
						listContainer.add( gprKline );
					}
				}
				groupingKLineByPrice.put(  output  , listContainer);
				 
			}
			
		}
		
		return groupingKLineByPrice;
	}
	
	/**
	 * merge to main price 
	 * @param kline
	 * @return
	 */

	private double searchGroupingLinePrice(Map<Double,List<GoupingPointRefStockDailyKLine>> groupingKLineByPrice , double input) {

		double max = StockBusinessHelper.getDiffRelativedPrice(input, StockBusinessHelper.MAX_DIFF);
		double min = StockBusinessHelper.getDiffRelativedPrice(input, StockBusinessHelper.MIN_DIFF);

		Set<Double> keyPrice = groupingKLineByPrice.keySet();
		
		// --- find the pattern price to the list ---
		Set<Double> patternPrice = new HashSet<Double>();
		for (Double p : keyPrice) {
			
			if (p <= max && p >= min) {
				patternPrice.add(p);
			}
		}
		
		// return the input price when not fould in price map
		if ( patternPrice.size() == 0) {
			return input;
		}
		
		// --- find the close price if have some pattern price
		double lastTmpDiff = 999999999;
		double outputprice = input;
		for (Iterator<Double> pattIter = patternPrice.iterator() ; pattIter.hasNext(); ) {
			double currentPrice = pattIter.next();
			double tmpDiff = Math.abs( currentPrice - input );
			if ( tmpDiff < lastTmpDiff ) {
				outputprice = currentPrice;
				lastTmpDiff = tmpDiff;
			}			
			pattIter.remove();
		}
		
		
		return outputprice;
	}

	
	private Map<Double, List<GoupingPointRefStockDailyKLine>> sortByKey(Map<Double, List<GoupingPointRefStockDailyKLine>> map) {
		List<Double> list = new LinkedList<Double>(map.keySet());
		Collections.sort(list, 
			new Comparator<Double>() {
				public int compare(Double o1, Double o2) {
					if ( o1 > o2) {
						return -1;
					} if (o1 < o2) {
						return 1;
					}
					
					return 0;
				}
			}
		);
		Map<Double, List<GoupingPointRefStockDailyKLine>> result = new LinkedHashMap<Double, List<GoupingPointRefStockDailyKLine>>();
		for (Iterator<Double> it = list.iterator(); it.hasNext();) {
			Double key = (Double)it.next();
			result.put(key, map.get(key));     
		}
		return result;
	} 
	
	
	
}
